/**
 * Copyright (C) 2011-2012 Turn, Inc.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.turn.ttorrent.tracker;

import com.turn.ttorrent.common.TorrentLoggerFactory;
import org.simpleframework.http.core.ContainerServer;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * BitTorrent tracker.
 * <p>
 * <p>
 * The tracker usually listens on port 6969 (the standard BitTorrent tracker
 * port). Torrents must be registered directly to this tracker with the
 * {@link #announce(TrackedTorrent torrent)}</code> method.
 * </p>
 *
 * @author mpetazzoni
 */
public class Tracker {

  private static final Logger logger = TorrentLoggerFactory.getLogger(Tracker.class);

  /**
   * Request path handled by the tracker announce request handler.
   */
  public static final String ANNOUNCE_URL = "/announce";

  /**
   * Default tracker listening port (BitTorrent's default is 6969).
   */
  public static final int DEFAULT_TRACKER_PORT = 6969;

  /**
   * Default server name and version announced by the tracker.
   */
  public static final String DEFAULT_VERSION_STRING = "BitTorrent Tracker (ttorrent)";

  private Connection connection;

  /**
   * The in-memory repository of torrents tracked.
   */
  private final TorrentsRepository myTorrentsRepository;

  private PeerCollectorThread myPeerCollectorThread;
  private boolean stop;
  private String myAnnounceUrl;
  private final int myPort;
  private SocketAddress myBoundAddress = null;
  private String trackerName;

  private final TrackerServiceContainer myTrackerServiceContainer;


  // 添加getter和setter方法
  public String getTrackerName() {
    return trackerName;
  }

  public void setTrackerName(String trackerName) {
    this.trackerName = trackerName;
  }

  private boolean isPublic = false; // 新增成员变量

  private String loadTrackerName() {
    File configFile = new File("tracker-config.properties");
    if (configFile.exists()) {
      Properties props = new Properties();
      FileInputStream fis = null;
      try {
        fis = new FileInputStream(configFile);
        props.load(fis);

        // 加载 isPublic 参数
        String isPublicStr = props.getProperty("tracker.ispublic", "false");
        this.isPublic = Boolean.parseBoolean(isPublicStr);

        return props.getProperty("tracker.name", "unknown-tracker");
      } catch (IOException e) {
        logger.warn("Could not load tracker configuration: {}", e.getMessage());
      } finally {
        if (fis != null) {
          try {
            fis.close();
          } catch (IOException e) {
            logger.warn("Could not close config file input stream", e);
          }
        }
      }
    }
    return "unknown-tracker";
  }


  /**
   * Create a new BitTorrent tracker listening at the given address.
   *
   * @throws IOException Throws an <em>IOException</em> if the tracker
   *                     cannot be initialized.
   */
  // 在构造函数中初始化trackerName
  public Tracker(int port) throws IOException {
    this(port,
            getDefaultAnnounceUrl(new InetSocketAddress(InetAddress.getLocalHost(), port)).toString()
    );
    this.trackerName = loadTrackerName();
    // 在加载完tracker名称后立即报告启动
    reportStartup();
  }

  public Tracker(int port, String announceURL) throws IOException {
    myPort = port;
    myAnnounceUrl = announceURL;
    this.trackerName = loadTrackerName();
    myTorrentsRepository = new TorrentsRepository(10);
    final TrackerRequestProcessor requestProcessor = new TrackerRequestProcessor(myTorrentsRepository);
    requestProcessor.setTrackerName(this.trackerName);  // 现在这行代码可以正常工作了
    myTrackerServiceContainer = new TrackerServiceContainer(requestProcessor,
            new MultiAnnounceRequestProcessor(requestProcessor));
    myPeerCollectorThread = new PeerCollectorThread(myTorrentsRepository);
    // 在构造函数的最后报告启动
    reportStartup();
  }

  public Tracker(int port, String announceURL, TrackerRequestProcessor requestProcessor, TorrentsRepository torrentsRepository) throws IOException {
    myPort = port;
    myAnnounceUrl = announceURL;
    myTorrentsRepository = torrentsRepository;
    myTrackerServiceContainer = new TrackerServiceContainer(requestProcessor, new MultiAnnounceRequestProcessor(requestProcessor));
    myPeerCollectorThread = new PeerCollectorThread(myTorrentsRepository);
  }

  /**
   * Returns the full announce URL served by this tracker.
   * <p>
   * <p>
   * This has the form http://host:port/announce.
   * </p>
   */
  private static URL getDefaultAnnounceUrl(InetSocketAddress address) {
    try {
      return new URL("http",
              address.getAddress().getCanonicalHostName(),
              address.getPort(),
              ANNOUNCE_URL);
    } catch (MalformedURLException mue) {
      logger.error("Could not build tracker URL: {}!", mue, mue);
    }

    return null;
  }

  private void reportStartup() {
    try {
      // 根据 isPublic 参数选择 IP 获取方法
      String trackerIp;

      if (isPublic) {
        // 公网 Tracker：通过第三方服务获取公网 IP
        trackerIp = getPublicIpAddress();
      } else {
        // 局域网 Tracker：获取本地 IP
        trackerIp = getLocalIpAddress();
      }

      URL url = new URL("http://116.205.119.11:5111/api/tracker/startup");
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("POST");
      conn.setRequestProperty("Content-Type", "application/json");
      conn.setDoOutput(true);

      // 创建JSON数据，添加 isPublic 字段
      String jsonInputString = String.format(
              "{\"tracker_name\":\"%s\",\"ip\":\"%s\",\"port\":%d,\"is_public\":%b}",
              this.trackerName,
              trackerIp,
              myPort,
              this.isPublic
      );

      // 发送数据
      OutputStream os = null;
      try {
        os = conn.getOutputStream();
        byte[] input = jsonInputString.getBytes("utf-8");
        os.write(input, 0, input.length);
      } finally {
        if (os != null) {
          try {
            os.close();
          } catch (IOException e) {
            // 忽略异常
          }
        }
      }

      // 获取响应
      int responseCode = conn.getResponseCode();
      if (responseCode != 200) {
        logger.warn("Failed to report tracker startup, status code: {}", responseCode);
      }

      conn.disconnect();
    } catch (Exception e) {
      logger.warn("Failed to report tracker startup: {}", e.getMessage());
    }
  }

  /**
   * 获取本地IP地址（适用于局域网）
   */
  private String getLocalIpAddress() {
    try {
      // 优先尝试获取非回环的IPv4地址
      Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
      while (networkInterfaces.hasMoreElements()) {
        NetworkInterface ni = networkInterfaces.nextElement();
        if (ni.isLoopback() || !ni.isUp()) {
          continue;
        }

        Enumeration<InetAddress> addresses = ni.getInetAddresses();
        while (addresses.hasMoreElements()) {
          InetAddress addr = addresses.nextElement();
          if (addr instanceof Inet4Address) {
            return addr.getHostAddress();
          }
        }
      }

      // 如果上面没找到，就使用简单方法（可能返回回环地址127.0.0.1）
      return InetAddress.getLocalHost().getHostAddress();
    } catch (Exception e) {
      logger.warn("Failed to get local IP address: {}", e.getMessage());
      return "127.0.0.1";
    }
  }

  /**
   * 获取公网IP地址（适用于公网服务器）
   */
  private String getPublicIpAddress() {
    String[] ipServices = {
            "https://api.ipify.org",
            "http://checkip.amazonaws.com",
            "https://ipinfo.io/ip",
            "https://api.myip.com"
    };

    for (String service : ipServices) {
      try {
        URL url = new URL(service);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        int responseCode = conn.getResponseCode();
        if (responseCode == 200) {
          BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
          String ipLine = in.readLine().trim();
          in.close();

          // 简单验证返回的是否为有效IP
          if (ipLine.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
            logger.info("获取到公网IP: {}", ipLine);
            return ipLine;
          }
        }
      } catch (Exception e) {
        logger.warn("从服务 {} 获取公网IP失败: {}", service, e.getMessage());
      }
    }

    // 如果所有服务都失败，回退到本地IP
    logger.warn("所有公网IP获取服务都失败，回退到使用本地IP");
    return getLocalIpAddress();
  }


  public String getAnnounceUrl() {
    return myAnnounceUrl;
  }

  public URI getAnnounceURI() {
    try {
      URL announceURL = new URL(getAnnounceUrl());
      if (announceURL != null) {
        return announceURL.toURI();
      }
    } catch (URISyntaxException e) {
      logger.error("Cannot convert announce URL to URI", e);
    } catch (MalformedURLException e) {
      logger.error("Cannot create URL from announceURL", e);
    }
    return null;
  }

  /**
   * Start the tracker thread.
   */
  public void start(final boolean startPeerCleaningThread) throws IOException {
    logger.info("Starting BitTorrent tracker on {}...",
            getAnnounceUrl());
    connection = new SocketConnection(new ContainerServer(myTrackerServiceContainer));

    List<SocketAddress> tries = new ArrayList<SocketAddress>() {{
      try {
        add(new InetSocketAddress(InetAddress.getByAddress(new byte[4]), myPort));
      } catch (Exception ex) {
      }
      try {
        add(new InetSocketAddress(InetAddress.getLocalHost(), myPort));
      } catch (Exception ex) {
      }
      try {
        add(new InetSocketAddress(InetAddress.getByName(new URL(getAnnounceUrl()).getHost()), myPort));
      } catch (Exception ex) {
      }
    }};

    boolean started = false;
    for (SocketAddress address : tries) {
      try {
        if ((myBoundAddress = connection.connect(address)) != null) {
          logger.info("Started torrent tracker on {}", address);
          started = true;
          break;
        }
      } catch (IOException ioe) {
        logger.info("Can't start the tracker using address{} : ", address.toString(), ioe.getMessage());
      }
    }
    if (!started) {
      logger.error("Cannot start tracker on port {}. Stopping now...", myPort);
      stop();
      return;
    }
    if (startPeerCleaningThread) {
      if (myPeerCollectorThread == null || !myPeerCollectorThread.isAlive() || myPeerCollectorThread.getState() != Thread.State.NEW) {
        myPeerCollectorThread = new PeerCollectorThread(myTorrentsRepository);
      }

      myPeerCollectorThread.setName("peer-peerCollectorThread:" + myPort);
      myPeerCollectorThread.start();
    }
  }

  /**
   * Stop the tracker.
   * <p>
   * <p>
   * This effectively closes the listening HTTP connection to terminate
   * the service, and interrupts the peer myPeerCollectorThread thread as well.
   * </p>
   */
  public void stop() {
    this.stop = true;

    try {
      this.connection.close();
      logger.info("BitTorrent tracker closed.");
    } catch (IOException ioe) {
      logger.error("Could not stop the tracker: {}!", ioe.getMessage());
    }

    if (myPeerCollectorThread != null && myPeerCollectorThread.isAlive()) {
      myPeerCollectorThread.interrupt();
      try {
        myPeerCollectorThread.join();
      } catch (InterruptedException e) {
        //
      }
      logger.info("Peer collection terminated.");
    }
  }

  /**
   * Announce a new torrent on this tracker.
   * <p>
   * <p>
   * The fact that torrents must be announced here first makes this tracker a
   * closed BitTorrent tracker: it will only accept clients for torrents it
   * knows about, and this list of torrents is managed by the program
   * instrumenting this Tracker class.
   * </p>
   *
   * @param torrent The Torrent object to start tracking.
   * @return The torrent object for this torrent on this tracker. This may be
   * different from the supplied Torrent object if the tracker already
   * contained a torrent with the same hash.
   */
  public synchronized TrackedTorrent announce(TrackedTorrent torrent) {
    TrackedTorrent existing = myTorrentsRepository.getTorrent(torrent.getHexInfoHash());

    if (existing != null) {
      logger.warn("Tracker already announced torrent with hash {}.", existing.getHexInfoHash());
      return existing;
    }

    myTorrentsRepository.putIfAbsent(torrent.getHexInfoHash(), torrent);
    logger.info("Registered new torrent with hash {}.", torrent.getHexInfoHash());
    return torrent;
  }

  /**
   * Set to true to allow this tracker to track external torrents (i.e. those that were not explicitly announced here).
   *
   * @param acceptForeignTorrents true to accept foreign torrents (false otherwise)
   */
  public void setAcceptForeignTorrents(boolean acceptForeignTorrents) {
    myTrackerServiceContainer.setAcceptForeignTorrents(acceptForeignTorrents);
  }

  /**
   * @return all tracked torrents.
   */
  public Collection<TrackedTorrent> getTrackedTorrents() {
    return Collections.unmodifiableCollection(myTorrentsRepository.getTorrents().values());
  }

  public TrackedTorrent getTrackedTorrent(String hash) {
    return myTorrentsRepository.getTorrent(hash);
  }

  public void setAnnounceInterval(int announceInterval) {
    myTrackerServiceContainer.setAnnounceInterval(announceInterval);
  }

  public void setPeerCollectorExpireTimeout(int expireTimeout) {
    myPeerCollectorThread.setTorrentExpireTimeoutSec(expireTimeout);
  }
}
